/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package edu.pdx.cs.guavadata;

import edu.pdx.cs.guavadata.data.Schema;
import edu.pdx.cs.guavadata.data.Scheme;
import edu.pdx.cs.guavadata.domain.Domain;
import edu.pdx.cs.guavadata.domain.UniqueIdentifier;
import edu.pdx.cs.guavadata.query.Operator;
import edu.pdx.cs.guavadata.subclass.ControlType;
import edu.pdx.cs.guavadata.subclass.DataColumn;
import edu.pdx.cs.guavadata.subclass.DataSet;
import edu.pdx.cs.guavadata.subclass.DataTable;
import edu.pdx.cs.guavadata.subclass.LaunchType;
import java.awt.Dimension;
import java.awt.Point;
import java.io.File;
import java.io.StringReader;
import java.util.List;
import java.util.ArrayList;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
/**
 * represent tree of a component
 * @author VuTran
 */
public class GTree implements Guava {

    List<GTree> gt;
    public GTree parent;
    public Class ctl;
    private LaunchType ltype;
    private ControlType ctype;
    private String gtext;
    private String gname;
    private Point glocation;
    private Dimension gsize;
    private String gdefault;
    private String gtooltip;
    private Domain gdomain;
    
    public GTree(Guava g) {
        this.gt = new ArrayList<GTree>();
        this.parent = null;
        this.ctl = g.getClass();
        this.ltype = g.getLType();
        this.ctype = g.getCType();
        this.gtext = g.getGText();
        this.gname = g.getGName();
        this.glocation = g.getGLocation();
        this.gsize = g.getGSize();
        this.gdefault = g.getGDefault();
        this.gtooltip = g.getGToolTip();
        this.gdomain = g.getGDomain();
    }
    
    public GQueryNode toBuildTree() {
        // create the first treenode-root
        GQueryNode rootnode = new GQueryNode(this);
        this.addNodes(rootnode);
        return rootnode;
    }
    
    private void addNodes(GQueryNode node) {
        if ((node.g.getCType() == ControlType.Entity) || (node.g.getCType() == ControlType.Extern))
            attachSemanticSpike(node);
        for (GTree temp : this.gt) {
            GQueryNode newNode = new GQueryNode(temp);
            newNode.g.parent = node.g;
            temp.addNodes(newNode);
            
            // attach child node to its parent
           
        }
    }
    
    private void attachSemanticSpike(GQueryNode node) {
        
    }

    public Schema toSchema() {
        Schema curr = new Schema();
        try {
            if (this.getCType() != ControlType.Entity)
                throw new Exception("Method toSchema can only be called on " +
                        "Entity nodes, and node " + this.getGName() + " is of category " + this.getCType().toString());

            // New table name comes directly from the name of the GTree node
            Scheme temp = curr.newScheme(this.getGName(), null);

            // Add new artificial primary key column
            // Since this is the root table, there are no fk's
            temp.newColumn("id", new UniqueIdentifier(), true, false);

            for (GTree t : this.gt) {
                t.parseSchema(curr, temp, LaunchType.NA);
            }
        } catch (Exception e) {
            System.err.println("ERROR: " + e.toString());
        }
        return curr;
    }

    private void parseSchema(Schema curr, Scheme table, LaunchType rel) {
        switch (this.getCType()) {
            case None:
                for (GTree t : this.gt)
                    t.parseSchema(curr, table, this.getLType());
                break;

            case Attribute:
                table.newColumn(this.getGName(), this.getGDomain(), false, false);
                for (GTree t : this.gt)
                    t.parseSchema(curr, table, this.getLType());
                break;

            case Container:
                for (GTree t : this.gt)
                    t.parseSchema(curr, table, rel);
                break;

            case Entity:
                Scheme newTable = curr.newScheme(this.getGName(), table);
                if (rel == LaunchType.Multi) {
                    newTable.newColumn("id", new UniqueIdentifier(), true, false);
                    newTable.newColumn("fk", new UniqueIdentifier(), false, true);
                } else if (rel == LaunchType.Single) {
                    newTable.newColumn("id", new UniqueIdentifier(), true, true);
                } else if (rel == LaunchType.NA) {
                    newTable.newColumn("id", new UniqueIdentifier(), true, false);
                    newTable.newColumn("fk", new UniqueIdentifier(), false, true);
                }

                for (GTree t : this.gt)
                    t.parseSchema(curr, newTable, this.getLType());
                break;

            case Extern:
                newTable = curr.newScheme(this.getGName(), table);
                newTable.newColumn("id", new UniqueIdentifier(), true, true);
                for (GTree t : this.gt) {
                    t.parseSchema(curr, newTable, LaunchType.NA);
                }
                break;
        }
        return;
    }
    /*
     * Generate root and its components
     */
    public static GTree generate(Guava gin) {
        GTree root = new GTree(gin);

        for (Guava g : gin.getGComponents()) {
            GTree temp = GTree.generate(g);
            temp.parent = root;
            root.gt.add(temp);
        }

        return root;
    }

    public DataSet toDataSet() {
        try {
            if (this.ctype != ControlType.Entity)
                throw new Exception("Method ToDataSet can only be called on Entity nodes, and node " +
                        this.gname + " is of category " + this.ctype.toString());
        } catch (Exception e) {
            System.err.println("ERROR: " + e.toString());
        }

        DataSet ds = new DataSet("Naive");
        parseDataSet(ds, null, LaunchType.Root);
        return ds;
    }

    private void parseDataSet(DataSet ds, DataTable dt, LaunchType rel) {
        DataColumn dc;
        switch (this.ctype) {
            case None:
                for (GTree t : this.gt) {
                    t.parseDataSet(ds, dt, this.ltype);
                }
                break;

            case Attribute:
                if (!(dt.columns.contains(this.gname))) {
                    dc = new DataColumn(this.gname, this.gdomain.getNaiveType());
                    dc.extendedProperties.put(Operator.domainFlag, this.gdomain);
                    dc.extendedProperties.put(Operator.guavaFlag, this);
                    dt.columns.add(dc);
                }
                for (GTree t : this.gt) {
                    t.parseDataSet(ds, dt, this.ltype);
                }
                break;

            case Container:
                for (GTree t : this.gt) {
                    t.parseDataSet(ds, dt, rel);
                }
                break;

            case Entity:
                if (!(ds.containsTable(this.gname))) {
                    DataTable newTable = new DataTable(gname);
                    ds.tables.add(newTable);

                    if (rel == LaunchType.Multi) {
                        dc = new DataColumn("id", UUID.class);
                        dc.extendedProperties.put(Operator.domainFlag, new UniqueIdentifier());
                        dc.extendedProperties.put(Operator.guavaFlag, this);
                        newTable.columns.add(dc);

                        dc = new DataColumn("fk", UUID.class);
                        dc.extendedProperties.put(Operator.domainFlag, new UniqueIdentifier());
                        newTable.columns.add(dc);
                    } else if (rel == LaunchType.Single) {
                        dc = new DataColumn("id", UUID.class);
                        dc.extendedProperties.put(Operator.domainFlag, new UniqueIdentifier());
                        dc.extendedProperties.put(Operator.guavaFlag, this);
                        newTable.columns.add(dc);
                    } else if (rel == LaunchType.NA) {
                        dc = new DataColumn("id", UUID.class);
                        dc.extendedProperties.put(Operator.domainFlag, new UniqueIdentifier());
                        dc.extendedProperties.put(Operator.guavaFlag, this);
                        newTable.columns.add(dc);

                        dc = new DataColumn("fk", UUID.class);
                        dc.extendedProperties.put(Operator.domainFlag, new UniqueIdentifier());
                        newTable.columns.add(dc);
                    } else {
                        dc = new DataColumn("id", UUID.class);
                        dc.extendedProperties.put(Operator.domainFlag, new UniqueIdentifier());
                        dc.extendedProperties.put(Operator.guavaFlag, this);
                        newTable.columns.add(dc);
                    }

                    for (GTree t : this.gt)
                        t.parseDataSet(ds, newTable, this.ltype);
                }
                break;
            case Extern:
                if (!(ds.containsTable(this.gname))) {
                    DataTable newTable = new DataTable(gname);
                    ds.tables.add(newTable);
                    dc = new DataColumn("id", UUID.class);
                    dc.extendedProperties.put(Operator.domainFlag, new UniqueIdentifier());
                    dc.extendedProperties.put(Operator.guavaFlag, this);
                    newTable.columns.add(dc);
                    for (GTree t : this.gt) {
                        t.parseDataSet(ds, newTable, LaunchType.NA);
                    }
                }
                break;
        }
        return;
    }

    public Document toXml() {
        Document doc = null;
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            InputSource is = new InputSource();
            is.setCharacterStream(new StringReader("<GTree></GTree>"));

            doc = db.parse(is);
            this.parseXml(doc, (Element) doc.getFirstChild());
        } catch (Exception ex) {
            System.err.println("ERROR: " + ex.toString());
        }
        return doc;
    }

    public void parseXml(Document doc, Element e) {
        Element ee = doc.createElement(this.getGName());
        ee.setAttribute("Control", this.ctl.toString());
        ee.setAttribute("ControlType", this.getCType().toString());
        ee.setAttribute("Width", String.valueOf(this.getGSize().getWidth()));
        ee.setAttribute("Height", String.valueOf(this.getGSize().getHeight()));
        ee.setAttribute("X", String.valueOf(this.getGLocation().getX()));
        ee.setAttribute("Y", String.valueOf(this.getGLocation().getY()));
        ee.setAttribute("Text", this.getGText());
        if (this.getGDomain() != null) {
            ee.setAttribute("Type", this.getGDomain().toString());
        } else {
            ee.setAttribute("Type", null);
        }
        
        e.appendChild(ee);
        
        for (GTree g : this.gt) {
            g.parseXml(doc, ee);
        }
    }

    public void saveToXml(String docName) {
        try {
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            DOMSource source = new DOMSource(this.toXml());
            StreamResult result = new StreamResult(new File(docName));
            transformer.transform(source, result);
        } catch (Exception ex) {
            System.err.println("ERROR: " + ex.getMessage());
        }
    }

   // <editor-fold defaultstate="collapsed" desc=" Guava Member ">     
    public LaunchType getLType() {
        return this.ltype;
    }

    public ControlType getCType() {
        return this.ctype;
    }

    public String getGText() {
        return this.gtext;
    }

    public String getGName() {
        return this.gname;
    }

    public void setGName(String GName) {
        try {
            throw new Exception("Cannot set properties of a Guava component through a tree element.");
        } catch (Exception ex) {
            System.err.println("ERROR: Cannot set properties of a Guava component through a tree element.");
        }
    }

    public Point getGLocation() {
        return this.glocation;
    }

    public Dimension getGSize() {
        return this.gsize;
    }

    public List<Guava> getGComponents() {
        List<Guava> lg = new ArrayList<Guava>();
        for (GTree g : this.gt) {
            lg.add(g);
        }
        return lg;
    }

    public void gBind(String table, int i) {
        return;
    }

    public void gBindFromDatabase(String table, int i) {
        return;
    }

    public String getGDefault() {
        return this.gdefault;
    }

    public String getGToolTip() {
        return this.gtooltip;
    }

    public Domain getGDomain() {
        return this.gdomain;
    }

    public Guava get(String s) {
        for (Guava g : this.getGComponents())
            if (g.getGName().equals(s))
                return g;
        return null;
    }
    //  </editor-fold> 
}
